package com.leansoft.bigqueue.page;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MappedPageImpl implements IMappedPage, Closeable {

    private final static Logger logger = LoggerFactory.getLogger(MappedPageImpl.class);

    private ThreadLocalByteBuffer threadLocalBuffer; // 线程局部 local buffer
    private volatile boolean dirty = false; // 是否脏页
    private volatile boolean closed = false; // 是否关闭
    private String pageFile; // back file
    private long index; // 索引

    public MappedPageImpl(MappedByteBuffer mbb, String pageFile, long index) {
        this.threadLocalBuffer = new ThreadLocalByteBuffer(mbb);
        this.pageFile = pageFile;
        this.index = index;
    }

    public void close() throws IOException {
        synchronized (this) {
            if (closed) return;

            flush(); // 关闭之前，先刷磁盘

            MappedByteBuffer srcBuf = (MappedByteBuffer) threadLocalBuffer.getSourceBuffer();
            unmap(srcBuf);

            this.threadLocalBuffer = null; // hint GC

            closed = true;
            if (logger.isDebugEnabled()) {
                logger.debug("Mapped page for " + this.pageFile + " was just unmapped and closed.");
            }
        }
    }

    @Override
    public void setDirty(boolean dirty) {
        this.dirty = dirty;
    }

    @Override
    public void flush() {
        synchronized (this) {
            if (closed) return;
            if (dirty) {
                MappedByteBuffer srcBuf = (MappedByteBuffer) threadLocalBuffer.getSourceBuffer();
                srcBuf.force(); // flush the changes 强行刷磁盘
                dirty = false; // 刷完以后，不脏了
                if (logger.isDebugEnabled()) {
                    logger.debug("Mapped page for " + this.pageFile + " was just flushed.");
                }
            }
        }
    }

    public byte[] getLocal(int position, int length) {
        ByteBuffer buf = this.getLocal(position);
        byte[] data = new byte[length];
        buf.get(data);
        return data;
    }

    @Override
    public ByteBuffer getLocal(int position) {
        ByteBuffer buf = this.threadLocalBuffer.get();
        buf.position(position);
        return buf;
    }

    private static void unmap(MappedByteBuffer buffer) {
        Cleaner.clean(buffer);
    }

    /**
     * Helper class allowing to clean direct buffers.
     * 清除直接缓冲区
     */
    private static class Cleaner {
        public static final boolean CLEAN_SUPPORTED;
        private static final Method directBufferCleaner;
        private static final Method directBufferCleanerClean;

        static {
            Method directBufferCleanerX = null;
            Method directBufferCleanerCleanX = null;
            boolean v;
            try {
                directBufferCleanerX = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner");
                directBufferCleanerX.setAccessible(true);
                directBufferCleanerCleanX = Class.forName("sun.misc.Cleaner").getMethod("clean");
                directBufferCleanerCleanX.setAccessible(true);
                v = true;
            } catch (Exception e) {
                v = false;
            }
            CLEAN_SUPPORTED = v;
            directBufferCleaner = directBufferCleanerX;
            directBufferCleanerClean = directBufferCleanerCleanX;
        }

        public static void clean(ByteBuffer buffer) {
            if (buffer == null) return;
            if (CLEAN_SUPPORTED && buffer.isDirect()) {
                try {
                    Object cleaner = directBufferCleaner.invoke(buffer);
                    directBufferCleanerClean.invoke(cleaner);
                } catch (Exception e) {
                    // silently ignore exception
                }
            }
        }
    }

    private static class ThreadLocalByteBuffer extends ThreadLocal<ByteBuffer> {
        private ByteBuffer _src;

        public ThreadLocalByteBuffer(ByteBuffer src) {
            _src = src;
        }

        public ByteBuffer getSourceBuffer() {
            return _src;
        }

        @Override
        protected synchronized ByteBuffer initialValue() {
            ByteBuffer dup = _src.duplicate();
            return dup;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    public String toString() {
        return "Mapped page for " + this.pageFile + ", index = " + this.index + ".";
    }

    @Override
    public String getPageFile() {
        return this.pageFile;
    }

    @Override
    public long getPageIndex() {
        return this.index;
    }
}
